﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DiagramDesigner.Helpers;
using DiagramDesigner;
using DemoApp.Persistence.Common;
using System.Threading.Tasks;
using System.Xml;
using System.IO;
using Newtonsoft.Json;

namespace DemoApp
{
    public class Window1ViewModel : INPCBase
    {

        private List<int> savedDiagrams = new List<int>();
        private int? savedDiagramId;
        private List<SelectableDesignerItemViewModelBase> itemsToRemove;
        private IMessageBoxService messageBoxService;
        private DiagramViewModel diagramViewModel = new DiagramViewModel();
        private bool isBusy = false;
        private int itemId;
        public string FilePath { get; set; }
        public string ComponentName { get; set; }

        public Window1ViewModel()
        {
            messageBoxService = ApplicationServicesProvider.Instance.Provider.MessageBoxService;
            
            ToolBoxViewModel = new ToolBoxViewModel();
            DiagramViewModel = new DiagramViewModel();

            DeleteSelectedItemsCommand = new SimpleCommand(ExecuteDeleteSelectedItemsCommand);
            CreateNewDiagramCommand = new SimpleCommand(ExecuteCreateNewDiagramCommand);
            SaveDiagramCommand = new SimpleCommand(ExecuteSaveDiagramCommand);
            StoreDiagramCommand = new SimpleCommand(ExecuteStoreDiagramCommand);
            BrowseDiagramCommand = new SimpleCommand(ExecuteBrowseDiagramCommand);
            LoadDiagramCommand = new SimpleCommand(ExecuteLoadDiagramCommand);
            LoadCloudCommand = new SimpleCommand(ExecuteLoadCloudCommand);

            //OrthogonalPathFinder is a pretty bad attempt at finding path points, it just shows you, you can swap this out with relative
            //ease if you wish just create a new IPathFinder class and pass it in right here
            ConnectorViewModel.PathFinder = new OrthogonalPathFinder();
            itemId = 0;
        }

        public SimpleCommand DeleteSelectedItemsCommand { get; private set; }
        public SimpleCommand CreateNewDiagramCommand { get; private set; }
        public SimpleCommand SaveDiagramCommand { get; private set; }
        public SimpleCommand StoreDiagramCommand { get; private set; }
        public SimpleCommand BrowseDiagramCommand { get; private set; }
        public SimpleCommand LoadDiagramCommand { get; private set; }
        public SimpleCommand LoadCloudCommand { get; private set; }
        public ToolBoxViewModel ToolBoxViewModel { get; private set; }


        public DiagramViewModel DiagramViewModel
        {
            get
            {
                return diagramViewModel;
            }
            set
            {
                if (diagramViewModel != value)
                {
                    diagramViewModel = value;
                    NotifyChanged("DiagramViewModel");
                }
            }
        }

        public bool IsBusy
        {
            get
            {
                return isBusy;
            }
            set
            {
                if (isBusy != value)
                {
                    isBusy = value;
                    NotifyChanged("IsBusy");
                }
            }
        }

        public List<int> SavedDiagrams
        {
            get
            {
                return savedDiagrams;
            }
            set
            {
                if (savedDiagrams != value)
                {
                    savedDiagrams = value;
                    NotifyChanged("SavedDiagrams");
                }
            }
        }

        public int? SavedDiagramId
        {
            get
            {
                return savedDiagramId;
            }
            set
            {
                if (savedDiagramId != value)
                {
                    savedDiagramId = value;
                    NotifyChanged("SavedDiagramId");
                }
            }
        }

        private void ExecuteDeleteSelectedItemsCommand(object parameter)
        {
            itemsToRemove = DiagramViewModel.SelectedItems;
            List<SelectableDesignerItemViewModelBase> connectionsToAlsoRemove = new List<SelectableDesignerItemViewModelBase>();

            foreach (var connector in DiagramViewModel.Items.OfType<ConnectorViewModel>())
            {
                if (ItemsToDeleteHasConnector(itemsToRemove, connector.SourceConnectorInfo))
                {
                    connectionsToAlsoRemove.Add(connector);
                }

                if (ItemsToDeleteHasConnector(itemsToRemove, (FullyCreatedConnectorInfo)connector.SinkConnectorInfo))
                {
                    connectionsToAlsoRemove.Add(connector);
                }

            }
            itemsToRemove.AddRange(connectionsToAlsoRemove);
            foreach (var selectedItem in itemsToRemove)
            {
                DiagramViewModel.RemoveItemCommand.Execute(selectedItem);
            }
        }

        private void ExecuteCreateNewDiagramCommand(object parameter)
        {
            //ensure that itemsToRemove is cleared ready for any new changes within a session
            itemsToRemove = new List<SelectableDesignerItemViewModelBase>();
            SavedDiagramId = null;
            DiagramViewModel.CreateNewDiagramCommand.Execute(null);

            itemId = 0;
            FilePath = "";
            NotifyChanged("FilePath");
        }

        private void ExecuteSaveDiagramCommand(object parameter)
        {
            if (!DiagramViewModel.Items.Any())
            {
                messageBoxService.ShowError("There must be at least one item in order save a diagram");
                return;
            }

            Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();

            dlg.FileName = "diagram";
            dlg.DefaultExt = ".xml";
            dlg.Filter = "XML Files (*.xml)|*.xml";
            Nullable<bool> result = dlg.ShowDialog();
            
            string filename = "", safefilename = "";
            if (result == true)
            {
                filename = dlg.FileName;
                safefilename = dlg.SafeFileName;
            }

            IsBusy = true;

            Task<int> task = Task.Factory.StartNew<int>(() =>
                {
                    SaveDiagramToXML(filename);
                    return 1;
                });
            task.ContinueWith((ant) =>
            {
                IsBusy = false;
                if (result == true)
                    messageBoxService.ShowInformation("Finished saving Diagram");

            }, TaskContinuationOptions.OnlyOnRanToCompletion);

            FilePath = "";
            NotifyChanged("FilePath");
        }

        private void ExecuteStoreDiagramCommand(object parameter)
        {
            string uri = "http://xman-ws.herokuapp.com/services/atomic/store";
            bool result = false;

            ComponentData data = new ComponentData(ComponentName);
            IUIVisualizerService visualiserService = ApplicationServicesProvider.Instance.Provider.VisualizerService;

            if (visualiserService.ShowDialog(data) == true)
            {
                ComponentName = data.ComponentName;
                result = true;
            }

            Task<int> task = Task.Factory.StartNew<int>(() =>
            {
                SaveDiagramToXML(ComponentName);
                WebServiceHelper.StoreXMLtoWebService(ComponentName, uri);
                DeleteXMLFile(ComponentName);

                return 1;
            });
            task.ContinueWith((ant) =>
            {
                IsBusy = false;
                if (result == true)
                    messageBoxService.ShowInformation(string.Format("Finished storing to : {0}", uri));

            }, TaskContinuationOptions.OnlyOnRanToCompletion);
        }

        private void SaveDiagramToXML(string xmlFileName)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.IndentChars = "\t";
            string filename = string.IsNullOrEmpty(xmlFileName) ? "atomic-component.xml" : xmlFileName;

            using (XmlWriter writer = XmlWriter.Create(filename, settings))
            {
                writer.WriteStartDocument();
                writer.WriteStartElement("atomic");
                writer.WriteAttributeString("xmlns", "mvvm", null, "mvvm.com/x-man");
                writer.WriteStartElement("name");
                writer.WriteString(Path.GetFileNameWithoutExtension(filename));
                writer.WriteEndElement();
                
                //Save all ComputationUnitViewModel
                foreach (var computationUnitItemVM in DiagramViewModel.Items.OfType<ComputationUnitViewModel>())
                {
                    if (computationUnitItemVM.Id == 0)
                        computationUnitItemVM.Id = ++itemId;

                    writer.WriteStartElement("language");
                    writer.WriteAttributeString("id", computationUnitItemVM.GetLanguageId().ToString());
                    writer.WriteString(computationUnitItemVM.GetLanguageName());
                    writer.WriteEndElement();
                    writer.WriteStartElement("computation-unit");
                    writer.WriteAttributeString("id", computationUnitItemVM.Id.ToString());

                    for (int i = 0; i < computationUnitItemVM.MethodNames.Count; i++)
                    {
                        writer.WriteStartElement("method");
                        writer.WriteAttributeString("name", computationUnitItemVM.MethodNames[i]);
                        writer.WriteString(computationUnitItemVM.MethodCodes[i]);
                        writer.WriteEndElement();
                    }

                    writer.WriteStartElement("app-info");
                    writer.WriteStartElement("mvvm", "info", null);
                    writer.WriteAttributeString("id", computationUnitItemVM.Id.ToString());
                    writer.WriteEndElement();
                    writer.WriteStartElement("mvvm", "coord", null);
                    writer.WriteAttributeString("top", computationUnitItemVM.Top.ToString());
                    writer.WriteAttributeString("left", computationUnitItemVM.Left.ToString());
                    writer.WriteEndElement();
                    writer.WriteEndElement();
                    writer.WriteEndElement();
                }

                //Save all ComputationUnitViewModel
                foreach (var servicesItemVM in DiagramViewModel.Items.OfType<ServicesViewModel>())
                {
                    if (servicesItemVM.Id == 0)
                        servicesItemVM.Id = ++itemId;

                    writer.WriteStartElement("service");
                    writer.WriteAttributeString("name", servicesItemVM.ServiceName);
                    writer.WriteAttributeString("id", servicesItemVM.Id.ToString());

                    writer.WriteStartElement("app-info");
                    writer.WriteStartElement("mvvm", "info", null);
                    writer.WriteAttributeString("id", servicesItemVM.Id.ToString());
                    writer.WriteEndElement();
                    writer.WriteStartElement("mvvm", "coord", null);
                    writer.WriteAttributeString("top", servicesItemVM.Top.ToString());
                    writer.WriteAttributeString("left", servicesItemVM.Left.ToString());
                    writer.WriteEndElement();
                    writer.WriteEndElement();

                    writer.WriteEndElement();
                }

                //Save all connections which should now have their Connection.DataItems filled in with correct Ids
                foreach (var connectionVM in DiagramViewModel.Items.OfType<ConnectorViewModel>())
                {
                    FullyCreatedConnectorInfo sinkConnector = connectionVM.SinkConnectorInfo as FullyCreatedConnectorInfo;

                    Connection connection = new Connection(
                        connectionVM.Id,
                        connectionVM.SourceConnectorInfo.DataItem.Id,
                        GetOrientationFromConnector(connectionVM.SourceConnectorInfo.Orientation),
                        GetTypeOfDiagramItem(connectionVM.SourceConnectorInfo.DataItem),
                        sinkConnector.DataItem.Id,
                        GetOrientationFromConnector(sinkConnector.Orientation),
                        GetTypeOfDiagramItem(sinkConnector.DataItem));

                    writer.WriteStartElement("connector");

                    writer.WriteStartElement("source");
                    writer.WriteAttributeString("id", connection.SourceId.ToString());
                    writer.WriteEndElement();

                    writer.WriteStartElement("target");
                    writer.WriteAttributeString("id", connection.SinkId.ToString());
                    writer.WriteEndElement();

                    writer.WriteStartElement("app-info");

                    writer.WriteStartElement("mvvm", "source-type", null);
                    writer.WriteString(connection.SourceType.Name);
                    writer.WriteEndElement();
                    writer.WriteStartElement("mvvm", "source-id", null);
                    writer.WriteString(connection.SourceId.ToString());
                    writer.WriteEndElement();
                    writer.WriteStartElement("mvvm", "source-orientation", null);
                    writer.WriteString(connection.SourceOrientation.ToString());
                    writer.WriteEndElement();

                    writer.WriteStartElement("mvvm", "destination-type", null);
                    writer.WriteString(connection.SinkType.Name);
                    writer.WriteEndElement();
                    writer.WriteStartElement("mvvm", "destination-id", null);
                    writer.WriteString(connection.SinkId.ToString());
                    writer.WriteEndElement();
                    writer.WriteStartElement("mvvm", "destination-orientation", null);
                    writer.WriteString(connection.SinkOrientation.ToString());
                    writer.WriteEndElement();

                    writer.WriteEndElement();

                    writer.WriteEndElement();
                }

                writer.WriteEndElement();
                writer.WriteEndDocument();
                writer.Flush();
            }
        }

        private void DeleteXMLFile(string xmlFileName)
        {
            try
            {
                File.Delete(xmlFileName);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        private void ExecuteBrowseDiagramCommand(object parameter)
        {
            Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();

            dlg.DefaultExt = ".xml";
            dlg.Filter = "XML Files (*.xml)|*.xml";
            Nullable<bool> result = dlg.ShowDialog();

            if (result == true)
            {
                FilePath = dlg.FileName;
                NotifyChanged("FilePath");
            }
        }

        private void ExecuteLoadDiagramCommand(object parameter)
        {
            IsBusy = true;
            if (String.IsNullOrEmpty(FilePath))
            {
                messageBoxService.ShowError("You need to select a diagram to load");
                return;
            }

            Task<DiagramViewModel> task = Task.Factory.StartNew<DiagramViewModel>(() =>
                {
                    return LoadDiagramFromXml();
                });
            task.ContinueWith((ant) =>
                {
                    this.DiagramViewModel = ant.Result;
                    IsBusy = false;
                    messageBoxService.ShowInformation("Finished loading Diagram");

                    if (FilePath.StartsWith("temp"))
                        File.Delete(FilePath);
 
                },TaskContinuationOptions.OnlyOnRanToCompletion);
        }

        private void ExecuteLoadCloudCommand(object parameter)
        {
            string componentListUri = "http://xman-ws.herokuapp.com/services/atomic";
            string componentXmlUri = "http://xman-ws.herokuapp.com/services/atomic/get";

            dynamic stuff = JsonConvert.DeserializeObject(WebServiceHelper.ConsumeWebService(componentListUri));
            Dictionary<int, string> components =
                JsonConvert.DeserializeObject<Dictionary<int, string>>(stuff.message.ToString());
            ComponentList list = new ComponentList(components);

            var visualiserService = ApplicationServicesProvider.Instance.Provider.VisualizerService;
            int selectedComponent;
            if (visualiserService.ShowListDialog(list.Components, out selectedComponent) == true)
            {
                string result = WebServiceHelper.ConsumeWebService(
                    String.Format("{0}/{1}/xml", componentXmlUri, selectedComponent));
                FilePath = "temp" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xml";
                File.WriteAllText(FilePath, result);

                this.DiagramViewModel = LoadDiagramFromXml();
            }
        }

        private DiagramViewModel LoadDiagramFromXml()
        {
            DiagramViewModel diagramViewModel = new DiagramViewModel();

            Dictionary<int, string> language = new Dictionary<int, string>();
            List<string> methodname = new List<string>(), methodcode = new List<string>();
            string servicename = "";
            double top = 0, left = 0;

            int sourceId = 0, destinationId = 0;
            Type sourceType = null, destinationType = null;
            Orientation sourceOrientation = Orientation.None, destinationOrientation = Orientation.None;

            bool isCompUnit = false;

            using (XmlReader reader = XmlReader.Create(FilePath))
            {
                while (reader.Read())
                {
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Element:
                            if (reader.Name.Equals("name"))
                            {
                                ComponentName = reader.ReadElementContentAsString();
                            }
                            if (reader.Name.Equals("computation-unit"))
                            {
                                isCompUnit = true;
                            }
                            else if (reader.Name.Equals("app-info") && isCompUnit == true)
                            {
                                reader.ReadToFollowing("mvvm:info");
                                reader.MoveToFirstAttribute();
                                itemId = Convert.ToInt32(reader.Value);
                                reader.ReadToFollowing("mvvm:coord");
                                reader.MoveToFirstAttribute();
                                top = Convert.ToDouble(reader.Value);
                                reader.MoveToNextAttribute();
                                left = Convert.ToDouble(reader.Value);
                            }
                            else if (reader.Name.Equals("method"))
                            {
                                reader.MoveToFirstAttribute();
                                methodname.Add(reader.Value);
                                reader.MoveToElement();
                                methodcode.Add(reader.ReadElementContentAsString());
                            }
                            else if (reader.Name.Equals("language"))
                            {
                                reader.MoveToFirstAttribute();
                                string langid = reader.Value;
                                reader.MoveToElement();
                                language.Add(Convert.ToInt32(langid), reader.ReadElementContentAsString());
                            }
                            else if (reader.Name.Equals("service"))
                            {
                                reader.MoveToFirstAttribute();
                                servicename = reader.Value;
                                reader.ReadToFollowing("mvvm:info");
                                reader.MoveToFirstAttribute();
                                itemId = Convert.ToInt32(reader.Value);
                                reader.ReadToFollowing("mvvm:coord");
                                reader.MoveToFirstAttribute();
                                top = Convert.ToDouble(reader.Value);
                                reader.MoveToNextAttribute();
                                left = Convert.ToDouble(reader.Value);
                            }
                            else if (reader.Name.Equals("connector"))
                            {
                                reader.ReadToFollowing("mvvm:source-type");
                                string type = reader.ReadElementContentAsString();
                                if (type.Equals("ComputationUnitItem"))
                                    sourceType = typeof(ComputationUnitItem);
                                else if (type.Equals("ServicesItem"))
                                    sourceType = typeof(ServicesItem);

                                reader.ReadToFollowing("mvvm:source-id");
                                sourceId = reader.ReadElementContentAsInt();

                                reader.ReadToFollowing("mvvm:source-orientation");
                                string orientation = reader.ReadElementContentAsString();
                                if (orientation.Equals("Top"))
                                    sourceOrientation = Orientation.Top;
                                else if (orientation.Equals("Bottom"))
                                    sourceOrientation = Orientation.Bottom;
                                else if (orientation.Equals("Left"))
                                    sourceOrientation = Orientation.Left;
                                else if (orientation.Equals("Right"))
                                    sourceOrientation = Orientation.Right;

                                reader.ReadToFollowing("mvvm:destination-type");
                                type = reader.ReadElementContentAsString();
                                if (type.Equals("ComputationUnitItem"))
                                    destinationType = typeof(ComputationUnitItem);
                                else if (type.Equals("ServicesItem"))
                                    destinationType = typeof(ServicesItem);

                                reader.ReadToFollowing("mvvm:destination-id");
                                destinationId = reader.ReadElementContentAsInt();

                                reader.ReadToFollowing("mvvm:destination-orientation");
                                orientation = reader.ReadElementContentAsString();
                                if (orientation.Equals("Top"))
                                    destinationOrientation = Orientation.Top;
                                else if (orientation.Equals("Bottom"))
                                    destinationOrientation = Orientation.Bottom;
                                else if (orientation.Equals("Left"))
                                    destinationOrientation = Orientation.Left;
                                else if (orientation.Equals("Right"))
                                    destinationOrientation = Orientation.Right;
                            }
                            break;
                        case XmlNodeType.EndElement:
                            if (reader.Name.Equals("computation-unit"))
                            {
                                if (language.Count == 0)
                                    language.Add(1, "Java");
                                ComputationUnitViewModel computationUnitDesignerItemViewModel =
                                    new ComputationUnitViewModel(itemId, diagramViewModel, left, top, language, methodname, methodcode);
                                diagramViewModel.Items.Add(computationUnitDesignerItemViewModel);
                                methodname = new List<String>();
                                methodcode = new List<String>();
                            }
                            else if (reader.Name.Equals("service"))
                            {
                                ServicesViewModel servicesDesignerItemViewModel =
                                    new ServicesViewModel(itemId, diagramViewModel, left, top, servicename);
                                diagramViewModel.Items.Add(servicesDesignerItemViewModel);
                            }
                            else if (reader.Name.Equals("connector"))
                            {
                                DesignerItemViewModelBase sourceItem = GetConnectorDataItem(diagramViewModel, sourceId, sourceType);
                                ConnectorOrientation sourceConnectorOrientation = GetOrientationForConnector(sourceOrientation);
                                FullyCreatedConnectorInfo sourceConnectorInfo = GetFullConnectorInfo(0, sourceItem, sourceConnectorOrientation);

                                DesignerItemViewModelBase sinkItem = GetConnectorDataItem(diagramViewModel, destinationId, destinationType);
                                ConnectorOrientation sinkConnectorOrientation = GetOrientationForConnector(destinationOrientation);
                                FullyCreatedConnectorInfo sinkConnectorInfo = GetFullConnectorInfo(0, sinkItem, sinkConnectorOrientation);

                                ConnectorViewModel connectionVM = new ConnectorViewModel(0, diagramViewModel, sourceConnectorInfo, sinkConnectorInfo);
                                diagramViewModel.Items.Add(connectionVM);
                            }
                            break;
                    }
                }
            }

            return diagramViewModel;
        }

        private FullyCreatedConnectorInfo GetFullConnectorInfo(int connectorId, DesignerItemViewModelBase dataItem, ConnectorOrientation connectorOrientation)
        {
            switch (connectorOrientation)
            {
                case ConnectorOrientation.Top:
                    return dataItem.TopConnector;
                case ConnectorOrientation.Left:
                    return dataItem.LeftConnector;
                case ConnectorOrientation.Right:
                    return dataItem.RightConnector;
                case ConnectorOrientation.Bottom:
                    return dataItem.BottomConnector;

                default:
                    throw new InvalidOperationException(
                        string.Format("Found invalid persisted Connector Orientation for Connector Id: {0}", connectorId));
            }
        }

        private Type GetTypeOfDiagramItem(DesignerItemViewModelBase vmType)
        {
            if (vmType is ComputationUnitViewModel)
                return typeof(ComputationUnitItem);
            if (vmType is ServicesViewModel)
                return typeof(ServicesItem);

            throw new InvalidOperationException(string.Format("Unknown diagram type. Currently only {0} and {1} are supported",
                typeof(ComputationUnitItem).AssemblyQualifiedName,
                typeof(ServicesViewModel).AssemblyQualifiedName
                ));

        }

        private DesignerItemViewModelBase GetConnectorDataItem(DiagramViewModel diagramViewModel, int conectorDataItemId, Type connectorDataItemType)
        {
            DesignerItemViewModelBase dataItem = null;

            if (connectorDataItemType == typeof(ComputationUnitItem))
            {
                dataItem = diagramViewModel.Items.OfType<ComputationUnitViewModel>().Single(x => x.Id == conectorDataItemId);
            }

            if (connectorDataItemType == typeof(ServicesItem))
            {
                dataItem = diagramViewModel.Items.OfType<ServicesViewModel>().Single(x => x.Id == conectorDataItemId);
            }
            return dataItem;
        }

        private Orientation GetOrientationFromConnector(ConnectorOrientation connectorOrientation)
        {
            Orientation result = Orientation.None;
            switch (connectorOrientation)
            {
                case ConnectorOrientation.Bottom:
                    result = Orientation.Bottom;
                    break;
                case ConnectorOrientation.Left:
                    result = Orientation.Left;
                    break;
                case ConnectorOrientation.Top:
                    result = Orientation.Top;
                    break;
                case ConnectorOrientation.Right:
                    result = Orientation.Right;
                    break;
            }
            return result;
        }

        private ConnectorOrientation GetOrientationForConnector(Orientation persistedOrientation)
        {
            ConnectorOrientation result = ConnectorOrientation.None;
            switch (persistedOrientation)
            {
                case Orientation.Bottom:
                    result = ConnectorOrientation.Bottom;
                    break;
                case Orientation.Left:
                    result = ConnectorOrientation.Left;
                    break;
                case Orientation.Top:
                    result = ConnectorOrientation.Top;
                    break;
                case Orientation.Right:
                    result = ConnectorOrientation.Right;
                    break;
            }
            return result;
        }

        private bool ItemsToDeleteHasConnector(List<SelectableDesignerItemViewModelBase> itemsToRemove, FullyCreatedConnectorInfo connector)
        {
            return itemsToRemove.Contains(connector.DataItem);
        }

    }
}
